001 /* 002 * Copyright 2004 Niclas Hedhman 003 * Copyright 2004-2005 Stephen McConnell 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. 015 * 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 package net.dpml.tools.tasks; 021 022 import java.io.File; 023 import java.io.FileFilter; 024 import java.io.FileInputStream; 025 import java.io.IOException; 026 import java.text.SimpleDateFormat; 027 import java.util.Calendar; 028 import java.util.Hashtable; 029 import java.util.Iterator; 030 import java.util.Map; 031 import java.util.Date; 032 import java.util.regex.Matcher; 033 import java.util.regex.Pattern; 034 035 import javax.xml.transform.Transformer; 036 import javax.xml.transform.TransformerFactory; 037 import javax.xml.transform.stream.StreamResult; 038 import javax.xml.transform.stream.StreamSource; 039 040 import net.dpml.transit.Transit; 041 042 import org.apache.tools.ant.BuildException; 043 import org.apache.tools.ant.Project; 044 import org.apache.tools.ant.taskdefs.Copy; 045 import org.apache.tools.ant.types.FileSet; 046 047 /** 048 * Site generation. 049 * 050 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 051 * @version 1.1.0 052 */ 053 public class DocTask extends GenericTask 054 { 055 private static final String ORG_NAME_VALUE = "The Digital Product Meta Library"; 056 private static final String DOC_TEMP_VALUE = "docs"; 057 private static final String DOC_SRC_VALUE = "docs"; 058 private static final String DOC_RESOURCES_VALUE = "resources"; 059 private static final String DOC_THEME_VALUE = "formal"; 060 private static final String DOC_FORMAT_VALUE = "html"; 061 private static final String DOC_DATE_FORMAT_VALUE = "yyyy-MMM-dd"; 062 private static final String DOC_STYLE_VALUE = "standard"; 063 private static final String DOC_ENTRY_VALUE = ""; 064 private static final String DOC_LOGO_RIGHT_FILE_VALUE = ""; 065 private static final String DOC_LOGO_RIGHT_URL_VALUE = ""; 066 private static final String DOC_LOGO_LEFT_FILE_VALUE = ""; 067 private static final String DOC_LOGO_LEFT_URL_VALUE = ""; 068 private static final String DOC_LOGO_MIDDLE_FILE_VALUE = ""; 069 private static final String DOC_LOGO_MIDDLE_URL_VALUE = ""; 070 private static final String DOC_BRAND_NAME_VALUE = "DPML"; 071 private static final String DOC_HOME_PATH_VALUE = "index.html"; 072 073 /** 074 * Constant organization key. 075 */ 076 public static final String ORG_NAME_KEY = "project.organization.name"; 077 078 /** 079 * Constant temp docs key. 080 */ 081 public static final String DOC_TEMP_KEY = "project.target.temp.docs"; 082 083 /** 084 * Constant docs src key. 085 */ 086 public static final String DOC_SRC_KEY = "project.docs.src"; 087 088 /** 089 * Constant docs resources key. 090 */ 091 public static final String DOC_RESOURCES_KEY = "project.docs.resources"; 092 093 /** 094 * Constant docs theme key. 095 */ 096 public static final String DOC_THEME_KEY = "project.docs.theme"; 097 098 /** 099 * Constant docs output format key. 100 */ 101 public static final String DOC_FORMAT_KEY = "project.docs.output.format"; 102 103 /** 104 * Constant docs date format key. 105 */ 106 public static final String DOC_DATE_FORMAT_KEY = "project.docs.date.format"; 107 108 /** 109 * Constant docs output style key. 110 */ 111 public static final String DOC_STYLE_KEY = "project.docs.output.style"; 112 113 /** 114 * Constant docs entry-point key. 115 */ 116 public static final String DOC_ENTRY_KEY = "project.docs.entry-point"; 117 118 /** 119 * Constant docs logo-right-file key. 120 */ 121 public static final String DOC_LOGO_RIGHT_FILE_KEY = "project.docs.logo.right.file"; 122 123 /** 124 * Constant docs logo-right url key. 125 */ 126 public static final String DOC_LOGO_RIGHT_URL_KEY = "project.docs.logo.right.url"; 127 128 /** 129 * Constant docs logo-left file key. 130 */ 131 public static final String DOC_LOGO_LEFT_FILE_KEY = "project.docs.logo.left.file"; 132 133 /** 134 * Constant docs logo-left url key. 135 */ 136 public static final String DOC_LOGO_LEFT_URL_KEY = "project.docs.logo.left.url"; 137 138 /** 139 * Constant docs logo-middle file key. 140 */ 141 public static final String DOC_LOGO_MIDDLE_FILE_KEY = "project.docs.logo.middle.file"; 142 143 /** 144 * Constant docs logo-middle url key. 145 */ 146 public static final String DOC_LOGO_MIDDLE_URL_KEY = "project.docs.logo.middle.url"; 147 148 /** 149 * Constant docs brand key. 150 */ 151 public static final String DOC_BRAND_NAME_KEY = "project.docs.brand.name"; 152 153 /** 154 * Constant docs anchor url key. 155 */ 156 public static final String DOC_ANCHOR_URL_KEY = "project.docs.anchor.url"; 157 158 /** 159 * Constant docs home page path. 160 */ 161 public static final String DOC_HOME_PATH_KEY = "project.docs.home.path"; 162 163 private String m_theme; 164 private File m_baseToDir; 165 private File m_baseSrcDir; 166 private File m_dest; 167 168 /** 169 * Return the assigned theme. 170 * @return the theme 171 */ 172 public String getTheme() 173 { 174 if( m_theme != null ) 175 { 176 return m_theme; 177 } 178 String theme = getProject().getProperty( DOC_THEME_KEY ); 179 if( null != theme ) 180 { 181 return theme; 182 } 183 else 184 { 185 return getResource().getProperty( DOC_THEME_KEY, "formal" ); 186 } 187 } 188 189 /** 190 * Set a directory for ultimate replication of the generated documentation. 191 * @param dir the utilimate destination directory 192 */ 193 public void setDest( File dir ) 194 { 195 m_dest = dir; 196 } 197 198 /** 199 * Set the doc theme. 200 * @param theme the theme name 201 */ 202 public void setTheme( final String theme ) 203 { 204 m_theme = theme; 205 } 206 207 /** 208 * Initialize the task. 209 * @exception BuildException if a build error occurs 210 */ 211 public void init() throws BuildException 212 { 213 if( !isInitialized() ) 214 { 215 super.init(); 216 final Project project = getProject(); 217 project.setNewProperty( ORG_NAME_KEY, ORG_NAME_VALUE ); 218 project.setNewProperty( DOC_SRC_KEY, DOC_SRC_VALUE ); 219 project.setNewProperty( DOC_RESOURCES_KEY, DOC_RESOURCES_VALUE ); 220 project.setNewProperty( DOC_FORMAT_KEY, DOC_FORMAT_VALUE ); 221 project.setNewProperty( DOC_DATE_FORMAT_KEY, DOC_DATE_FORMAT_VALUE ); 222 project.setNewProperty( DOC_STYLE_KEY, DOC_STYLE_VALUE ); 223 project.setNewProperty( DOC_ENTRY_KEY, DOC_ENTRY_VALUE ); 224 project.setNewProperty( DOC_TEMP_KEY, DOC_TEMP_VALUE ); 225 project.setNewProperty( DOC_LOGO_RIGHT_FILE_KEY, DOC_LOGO_RIGHT_FILE_VALUE ); 226 project.setNewProperty( DOC_LOGO_RIGHT_URL_KEY, DOC_LOGO_RIGHT_URL_VALUE ); 227 project.setNewProperty( DOC_LOGO_LEFT_FILE_KEY, DOC_LOGO_LEFT_FILE_VALUE ); 228 project.setNewProperty( DOC_LOGO_LEFT_URL_KEY, DOC_LOGO_LEFT_URL_VALUE ); 229 project.setNewProperty( DOC_LOGO_MIDDLE_FILE_KEY, DOC_LOGO_MIDDLE_FILE_VALUE ); 230 project.setNewProperty( DOC_LOGO_MIDDLE_URL_KEY, DOC_LOGO_MIDDLE_URL_VALUE ); 231 project.setNewProperty( DOC_BRAND_NAME_KEY, DOC_BRAND_NAME_VALUE ); 232 } 233 } 234 235 /** 236 * Execute the task. 237 */ 238 public void execute() 239 { 240 final Project project = getProject(); 241 final File srcDir = getContext().getTargetBuildDocsDirectory(); 242 if( !srcDir.exists() ) 243 { 244 return; 245 } 246 log( "Filtered source: " + srcDir.getAbsolutePath() ); 247 248 // 249 // create the temporary directory into which we generate the 250 // navigation structure (normally target/temp/docs) 251 // 252 253 final File temp = getContext().getTargetTempDirectory(); 254 final File destDir = new File( temp, "docs" ); 255 mkDir( destDir ); 256 257 // 258 // get the theme, output formats, etc. 259 // 260 261 final File docs = getContext().getTargetDocsDirectory(); 262 log( "Destination: " + docs.getAbsolutePath() ); 263 mkDir( docs ); 264 final String theme = getTheme(); 265 final String output = getOutputFormat(); 266 final String home = getHomePath(); 267 final File themeRoot = getThemesDirectory(); 268 final File themeDir = new File( themeRoot, theme + "/" + output ); 269 270 final File target = getContext().getTargetDirectory(); 271 final String resourcesPath = project.getProperty( DOC_RESOURCES_KEY ); 272 final File resources = new File( target, resourcesPath ); 273 274 log( "Year: " + getYear() ); 275 log( "Theme: " + themeDir ); 276 277 // 278 // initiate the transformation starting with the generation of 279 // the navigation structure based on the src directory content 280 // into the temporary destingation directory, copy the content 281 // sources to to the temp directory, transform the content and 282 // generated navigation in the temp dir using the selected them 283 // into the final docs directory, and copy over resources to 284 // the final docs directory 285 // 286 287 try 288 { 289 transformNavigation( themeDir, srcDir, destDir ); 290 copySources( srcDir, destDir ); 291 transformDocs( themeDir, destDir, docs ); 292 copyThemeResources( themeDir, docs ); 293 copySrcResources( resources, docs ); 294 } 295 catch( BuildException e ) 296 { 297 throw e; 298 } 299 catch( Throwable e ) 300 { 301 log( "XSLT execution failed: " + e.getMessage() ); 302 throw new BuildException( e ); 303 } 304 305 if( null != m_dest ) 306 { 307 copy( docs, m_dest, "**/*", "" ); 308 } 309 } 310 311 private File getThemesDirectory() 312 { 313 return new File( Transit.DPML_PREFS, "dpml/tools/themes" ); 314 } 315 316 private String getHomePath() 317 { 318 return getProject().getProperty( DOC_HOME_PATH_KEY ); 319 } 320 321 private String getOutputFormat() 322 { 323 return getProject().getProperty( DOC_FORMAT_KEY ); 324 } 325 326 private String getOutputStyle() 327 { 328 return getProject().getProperty( DOC_STYLE_KEY ); 329 } 330 331 private String getDateFormat() 332 { 333 return getProject().getProperty( DOC_DATE_FORMAT_KEY ); 334 } 335 336 private void transformNavigation( final File themeDir, final File source, final File dest ) 337 { 338 final File xslFile = new File( themeDir, "nav-aggregate.xsl" ); 339 if( !xslFile.exists() ) 340 { 341 return; // Theme may not use navigation. 342 } 343 log( "Transforming navigation." ); 344 try 345 { 346 transformTrax( 347 source, dest, xslFile, 348 "^.*/navigation.xml$", "", ".xml" ); 349 } 350 catch( BuildException e ) 351 { 352 throw e; 353 } 354 catch( Throwable e ) 355 { 356 final String error = 357 "Transformation failure: " + source; 358 throw new BuildException( error, e ); 359 } 360 } 361 362 private void copySources( final File source, final File dest ) 363 { 364 copy( source, dest, "**/*", "**/navigation.xml" ); 365 } 366 367 private void transformDocs( final File themeDir, final File build, final File docs ) 368 { 369 String style = getOutputStyle(); 370 File xslFile = new File( themeDir, style + ".xsl" ); 371 String output = getOutputFormat(); 372 log( "Transforming content." ); 373 transformTrax( 374 build, docs, xslFile, 375 "^.*\\.xml$", "^.*/navigation.xml$", "." + output ); 376 } 377 378 private void copySrcResources( final File resources, final File docs ) 379 { 380 copy( resources, docs, "**/*", "" ); 381 } 382 383 private void copyThemeResources( final File themeDir, final File docs ) 384 { 385 final File fromDir = new File( themeDir, "resources" ); 386 copy( fromDir, docs, "**/*", "" ); 387 } 388 389 private void copy( final File fromDir, final File toDir, final String includes, final String excludes ) 390 { 391 if( !fromDir.exists() ) 392 { 393 return; 394 } 395 396 final FileSet from = new FileSet(); 397 from.setDir( fromDir ); 398 from.setIncludes( includes ); 399 from.setExcludes( excludes ); 400 401 mkDir( toDir ); 402 403 final Copy copy = (Copy) getProject().createTask( "copy" ); 404 copy.setTodir( toDir ); 405 copy.addFileset( from ); 406 copy.setPreserveLastModified( true ); 407 copy.execute(); 408 } 409 410 411 private void transformTrax( 412 final File srcDir, final File toDir, final File xslFile, 413 final String includes, final String excludes, final String extension ) 414 throws BuildException 415 { 416 FileInputStream fis = null; 417 try 418 { 419 ClassLoader cl = getClass().getClassLoader(); 420 Thread.currentThread().setContextClassLoader( cl ); 421 StreamSource source = new StreamSource( xslFile ); 422 final TransformerFactory tfactory = TransformerFactory.newInstance(); 423 final Transformer transformer = tfactory.newTransformer( source ); 424 final RegexpFilter filter = new RegexpFilter( includes, excludes ); 425 426 m_baseToDir = toDir; 427 m_baseSrcDir = srcDir.getAbsoluteFile(); 428 String entrypoint = getEntryPoint(); 429 if( "".equals( entrypoint ) ) 430 { 431 transform( transformer, m_baseSrcDir, toDir, filter, extension ); 432 } 433 else 434 { 435 File fileToConvert = new File( m_baseSrcDir, entrypoint ); 436 transformFile( 437 transformer, fileToConvert, m_baseSrcDir, entrypoint, extension, m_baseToDir ); 438 } 439 } 440 catch( BuildException e ) 441 { 442 throw e; 443 } 444 catch( Exception e ) 445 { 446 throw new BuildException( e.getMessage(), e ); 447 } 448 finally 449 { 450 if( fis != null ) 451 { 452 try 453 { 454 fis.close(); 455 } 456 catch( IOException f ) 457 { 458 log( f.toString() ); 459 } 460 } 461 } 462 } 463 464 private void transform( final Transformer transformer, final File srcDir, final File toDir, 465 final FileFilter filter, final String extension ) 466 throws BuildException 467 { 468 boolean recursive = isRecursive(); 469 final File[] content = srcDir.listFiles( filter ); 470 for( int i=0; i < content.length; i++ ) 471 { 472 String base = content[i].getName(); 473 if( content[i].isDirectory() && recursive ) 474 { 475 final File newDest = new File( toDir, base ); 476 newDest.mkdirs(); 477 transform( transformer, content[i], newDest, filter, extension ); 478 } 479 if( content[i].isFile() ) 480 { 481 transformFile( transformer, content[i], srcDir, base, extension, toDir ); 482 } 483 } 484 } 485 486 private void transformFile( Transformer transformer, File content, File srcDir, 487 String base, String extension, File toDir ) 488 { 489 String userDir = System.getProperty( "user.dir" ); 490 System.setProperty( "user.dir", toDir.getAbsolutePath() ); 491 final String year = getYear(); 492 final String org = getOrganization(); 493 final String copyright = 494 "Copyright " 495 + year 496 + ", " 497 + org 498 + " All rights reserved."; 499 500 final String svnRoot = getProject().getProperty( DOC_ANCHOR_URL_KEY ); 501 final String svnSource = svnRoot + getRelSrcPath( srcDir ) + "/" + base; 502 503 final int pos = base.lastIndexOf( '.' ); 504 if( pos > 0 ) 505 { 506 base = base.substring( 0, pos ); 507 } 508 base = base + extension; 509 510 final File newDest = new File( toDir, base ); 511 final StreamSource xml = new StreamSource( content ); 512 final StreamResult out = new StreamResult( newDest ); 513 514 transformer.clearParameters(); 515 transformer.setParameter( "directory", getRelToPath( toDir ) ); 516 transformer.setParameter( "fullpath", getRelToPath( newDest ) ); 517 transformer.setParameter( "file", base ); 518 transformer.setParameter( "svn-location", svnSource ); 519 transformer.setParameter( "copyright", copyright ); 520 transformer.setParameter( 521 "logoright_file", 522 getProject().getProperty( DOC_LOGO_RIGHT_FILE_KEY ).trim() ); 523 transformer.setParameter( 524 "logoright_url", 525 getProject().getProperty( DOC_LOGO_RIGHT_URL_KEY ).trim() ); 526 transformer.setParameter( 527 "logoleft_file", 528 getProject().getProperty( DOC_LOGO_LEFT_FILE_KEY ).trim() ); 529 transformer.setParameter( 530 "logoleft_url", 531 getProject().getProperty( DOC_LOGO_LEFT_URL_KEY ).trim() ); 532 transformer.setParameter( 533 "logomiddle_file", 534 getProject().getProperty( DOC_LOGO_MIDDLE_FILE_KEY ).trim() ); 535 transformer.setParameter( 536 "logomiddle_url", 537 getProject().getProperty( DOC_LOGO_MIDDLE_URL_KEY ).trim() ); 538 transformer.setParameter( 539 "brand_name", 540 getProject().getProperty( DOC_BRAND_NAME_KEY ).trim() ); 541 transformer.setParameter( "generated_date", getNow() ); 542 setOtherProperties( transformer ); 543 try 544 { 545 transformer.transform( xml, out ); 546 } 547 catch( BuildException e ) 548 { 549 throw e; 550 } 551 catch( Throwable e ) 552 { 553 final String error = 554 "An error occured while attempting to transform document: " 555 + getRelToPath( newDest ); 556 throw new BuildException( error, e, getLocation() ); 557 } 558 finally 559 { 560 System.setProperty( "user.dir", userDir ); 561 } 562 } 563 564 private String getRelToPath( final File dir ) 565 { 566 final String basedir = m_baseToDir.getAbsolutePath(); 567 final String curdir = dir.getAbsolutePath(); 568 return curdir.substring( basedir.length() ); 569 } 570 571 private String getRelSrcPath( final File dir ) 572 { 573 final String basedir = m_baseSrcDir.getAbsolutePath(); 574 final String curdir = dir.getAbsolutePath(); 575 return curdir.substring( basedir.length() ); 576 } 577 578 /** 579 * Utility regualar expression filter. 580 */ 581 public class RegexpFilter implements FileFilter 582 { 583 private Pattern m_includes; 584 private Pattern m_excludes; 585 586 /** 587 * New filter creation. 588 * @param includes the includes 589 * @param excludes the excludes 590 */ 591 public RegexpFilter( final String includes, final String excludes ) 592 { 593 m_includes = Pattern.compile( includes ); 594 m_excludes = Pattern.compile( excludes ); 595 } 596 597 /** 598 * Test supplied file for acceptance. 599 * @param file the candidate 600 * @return TRUE if acceptable 601 */ 602 public boolean accept( final File file ) 603 { 604 final String basename = file.getName(); 605 606 if( basename.equals( ".svn" ) ) 607 { 608 return false; 609 } 610 611 if( basename.equals( "CVS" ) ) 612 { 613 return false; 614 } 615 616 if( file.isDirectory() ) 617 { 618 return true; 619 } 620 621 final String fullpath = file.getAbsolutePath().replace( '\\', '/' ); 622 623 Matcher m = m_includes.matcher( fullpath ); 624 if( !m.matches() ) 625 { 626 return false; 627 } 628 629 m = m_excludes.matcher( fullpath ); 630 return !m.matches(); 631 } 632 } 633 634 private String getYear() 635 { 636 String year = getProject().getProperty( "magic.year" ); 637 if( year != null ) 638 { 639 return year; 640 } 641 else 642 { 643 Calendar cal = Calendar.getInstance(); 644 return Integer.toString( cal.get( Calendar.YEAR ) ); 645 } 646 } 647 648 private String getOrganization() 649 { 650 return getProject().getProperty( ORG_NAME_KEY ); 651 } 652 653 private boolean isRecursive() 654 { 655 return "".equals( getEntryPoint() ); 656 } 657 658 private String getEntryPoint() 659 { 660 String point = getProject().getProperty( DOC_ENTRY_KEY ); 661 if( point == null ) 662 { 663 return ""; 664 } 665 return point; 666 } 667 668 private void setOtherProperties( Transformer transformer ) 669 { 670 String prefix = "project.docs.xsl."; 671 int prefixLen = prefix.length(); 672 673 Hashtable p = getProject().getProperties(); 674 Iterator list = p.entrySet().iterator(); 675 while( list.hasNext() ) 676 { 677 Map.Entry entry = (Map.Entry) list.next(); 678 String key = (String) entry.getKey(); 679 if( key.startsWith( prefix ) ) 680 { 681 String value = (String) entry.getValue(); 682 key = key.substring( prefixLen ); 683 transformer.setParameter( key, value ); 684 log( "Setting " + key + "=" + value ); 685 } 686 } 687 } 688 689 private String getNow() 690 { 691 Date now = new Date(); 692 SimpleDateFormat sdf = new SimpleDateFormat( getDateFormat() ); 693 String result = sdf.format( now ); 694 return result; 695 } 696 }